package com.managers
{
	import flash.display.Bitmap;
	import flash.display.Loader;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.media.Sound;
	import flash.net.FileReference;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.system.ImageDecodingPolicy;
	import flash.system.LoaderContext;
	import flash.utils.ByteArray;
	import flash.utils.Dictionary;
	import flash.utils.clearTimeout;
	import flash.utils.describeType;
	import flash.utils.getQualifiedClassName;
	import flash.utils.setTimeout;
	
	import mx.controls.Alert;

	public class AssetLoadManager
	{
		private var mVerbose:Boolean;
		private var mQueue:Array;
		private var mIsLoading:Boolean;
		private var mTimeoutID:uint;
		private var numElements:int;				//资源总数
		private var currentRatio : int;			//当前已加载资源数
		
		private var urlLoader:URLLoader;
		
		private var bitmapdict : Dictionary;			//图片资源
		
		
		public function AssetLoadManager()
		{
			bitmapdict = new Dictionary;
			mQueue = [];
			mIsLoading = false;
		}
		
		public function enqueue(...rawAssets):void
		{
			for each (var rawAsset:Object in rawAssets)
			{
				if (rawAsset is Array)
				{
					enqueue.apply(this, rawAsset);
				}
				else if (rawAsset is Class)
				{
					var typeXml:XML = describeType(rawAsset);
					var childNode:XML;
					
					if (mVerbose)
						log("Looking for static embedded assets in '" + 
							(typeXml.@name).split("::").pop() + "'"); 
					
					for each (childNode in typeXml.constant.(@type == "Class"))
					enqueueWithName(rawAsset[childNode.@name], childNode.@name);
					
					for each (childNode in typeXml.variable.(@type == "Class"))
					enqueueWithName(rawAsset[childNode.@name], childNode.@name);
				}
				else if (getQualifiedClassName(rawAsset) == "flash.filesystem::File")
				{
					if (!rawAsset["exists"])
					{
						log("File or directory not found: '" + rawAsset["url"] + "'");
					}
					else if (!rawAsset["isHidden"])
					{
						if (rawAsset["isDirectory"])
							enqueue.apply(this, rawAsset["getDirectoryListing"]());
						else
							enqueueWithName(rawAsset["url"]);
					}
				}
				else if (rawAsset is String)
				{
					enqueueWithName(rawAsset);
				}
				else
				{
					log("Ignoring unsupported asset type: " + getQualifiedClassName(rawAsset));
				}
			}
		}
		
		public function enqueueWithName(asset:Object, name:String=null):String
		{
			if (name == null) name = getName(asset);
			log("Enqueuing '" + name + "'");
			
			mQueue.push({
				name: name,
				asset: asset
			});
			
			return name;
		}
		
		protected function getName(rawAsset:Object):String
		{
			var matches:Array;
			var name:String;
			
			if (rawAsset is String || rawAsset is FileReference)
			{
				name = rawAsset is String ? rawAsset as String : (rawAsset as FileReference).name;
				name = name.replace(/%20/g, " "); // URLs use '%20' for spaces
				matches = /(.*[\\\/])?(.+)(\.[\w]{1,4})/.exec(name);
				
				if (matches && matches.length == 4) return matches[2];
				else throw new ArgumentError("Could not extract name from String '" + rawAsset + "'");
			}
			else
			{
				name = getQualifiedClassName(rawAsset);
				throw new ArgumentError("Cannot extract names for objects of type '" + name + "'");
			}
		}
		
		protected function log(message:String):void
		{
			if (mVerbose) trace("[AssetManager]", message);
		}
		
		public function loadQueue(onProgress:Function):void
		{
			if (mIsLoading)
				throw new Error("The queue is already being processed");
			
			var xmls:Vector.<XML> = new Vector.<XML>();
			var numElements:int = mQueue.length;
			var currentRatio:Number = 0.0;
			
			mIsLoading = true;
			resume();
			
			function resume():void
			{
				if (!mIsLoading)
					return;
				
				currentRatio = mQueue.length ? 1.0 - (mQueue.length / numElements) : 1.0;
				
				if (mQueue.length)
				{
					mTimeoutID = setTimeout(processNext, 1);
				}
				else
				{
					mIsLoading = false;
				}
				
				if (onProgress != null)
					onProgress(currentRatio);
			}
			
			function processNext():void
			{
				var assetInfo:Object = mQueue.pop();
				clearTimeout(mTimeoutID);
				processRawAsset(assetInfo.name, assetInfo.asset, xmls, progress, resume);
			}
			
			function progress(ratio:Number):void
			{
				onProgress(currentRatio + (1.0 / numElements) * Math.min(1.0, ratio) * 0.99);
			}
		}
		
		private function processRawAsset(name:String, rawAsset:Object, xmls:Vector.<XML>,
										 onProgress:Function, onComplete:Function):void
		{
			loadRawAsset(name, rawAsset, onProgress, process); 
			
			function process(asset:Object):void
			{
				var bytes:ByteArray;
				
				if (!mIsLoading)
				{
					onComplete();
				}
				else if (asset is Bitmap)
				{
//					texture = Texture.fromBitmap(asset as Bitmap, mUseMipMaps, false, mScaleFactor);
//					texture.root.onRestore = function():void
//					{
//						mNumLostTextures++;
//						loadRawAsset(name, rawAsset, null, function(asset:Object):void
//						{
//							try { texture.root.uploadBitmap(asset as Bitmap); }
//							catch (e:Error) { log("Texture restoration failed: " + e.message); }
//							
//							asset.bitmapData.dispose();
//							mNumRestoredTextures++;
//							
//							if (mNumLostTextures == mNumRestoredTextures)
//								dispatchEventWith(Event.TEXTURES_RESTORED);
//						});
//					};
//					
//					asset.bitmapData.dispose();
//					addTexture(name, texture);
					addBitmap(name,asset as Bitmap);
					onComplete();
				}
				else if (asset is ByteArray)
				{
//					bytes = asset as ByteArray;
//					
//					if (AtfData.isAtfData(bytes))
//					{
//						texture = Texture.fromAtfData(bytes, mScaleFactor, mUseMipMaps, onComplete);
//						texture.root.onRestore = function():void
//						{
//							mNumLostTextures++;
//							loadRawAsset(name, rawAsset, null, function(asset:Object):void
//							{
//								try { texture.root.uploadAtfData(asset as ByteArray, 0, true); }
//								catch (e:Error) { log("Texture restoration failed: " + e.message); }
//								
//								asset.clear();
//								mNumRestoredTextures++;
//								
//								if (mNumLostTextures == mNumRestoredTextures)
//									dispatchEventWith(Event.TEXTURES_RESTORED);
//							});
//						};
//						
//						bytes.clear();
//						addTexture(name, texture);
//					}
//					else if (byteArrayStartsWith(bytes, "{") || byteArrayStartsWith(bytes, "["))
//					{
//						addObject(name, JSON.parse(bytes.readUTFBytes(bytes.length)));
//						bytes.clear();
//						onComplete();
//					}
//					else if (byteArrayStartsWith(bytes, "<"))
//					{
//						process(new XML(bytes));
//						bytes.clear();
//					}
//					else
//					{
//						addByteArray(name, bytes);
//						onComplete();
//					}
				}
//				else if (asset is XML)
//				{
//					var xml:XML = asset as XML;
//					var rootNode:String = xml.localName();
//					
//					if (rootNode == "TextureAtlas" || rootNode == "font")
//						xmls.push(xml);
//					else
//						addXml(name, xml);
//					
//					onComplete();
//				}
				else if (asset == null)
				{
					onComplete();
				}
				else
				{
					log("Ignoring unsupported asset type: " + getQualifiedClassName(asset));
					onComplete();
				}
//				
//				// avoid that objects stay in memory (through 'onRestore' functions)
				asset = null;
				bytes = null;
			}
		}
		
		private function loadRawAsset(name:String, rawAsset:Object, 
									  onProgress:Function, onComplete:Function):void
		{
			var extension:String = null;
			var urlLoader:URLLoader = null;
			
			if (rawAsset is Class)
			{
				setTimeout(onComplete, 1, new rawAsset());
			}
			else if (rawAsset is String)
			{
				var url:String = rawAsset as String;
				extension = url.split(".").pop().toLowerCase().split("?")[0];
				
				urlLoader = new URLLoader();
				urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
				urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onIoError);
				urlLoader.addEventListener(ProgressEvent.PROGRESS, onLoadProgress);
				urlLoader.addEventListener(Event.COMPLETE, onUrlLoaderComplete);
				urlLoader.load(new URLRequest(url));
			}
			
			function onIoError(event:IOErrorEvent):void
			{
				log("IO error: " + event.text);
				onComplete(null);
			}
			
			function onLoadProgress(event:ProgressEvent):void
			{
				if (onProgress != null)
					onProgress(event.bytesLoaded / event.bytesTotal);
			}
			
			function onUrlLoaderComplete(event:Object):void
			{
				var bytes:ByteArray = urlLoader.data as ByteArray;
				
				urlLoader.removeEventListener(IOErrorEvent.IO_ERROR, onIoError);
				urlLoader.removeEventListener(ProgressEvent.PROGRESS, onLoadProgress);
				urlLoader.removeEventListener(Event.COMPLETE, onUrlLoaderComplete);
				
				switch (extension)
				{
					case "jpg":
					case "jpeg":
					case "png":
					case "swf":
					case "gif":
						var loaderContext:LoaderContext = new LoaderContext();
						var loader:Loader = new Loader();
						loaderContext.imageDecodingPolicy = ImageDecodingPolicy.ON_LOAD;
						loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaderComplete);
						loader.loadBytes(bytes, loaderContext);
					default: // any XML / JSON / binary data 
						onComplete(bytes);
						break;
				}
				function onLoaderComplete(event:Object):void
				{
					urlLoader.data.clear();
					event.target.removeEventListener(Event.COMPLETE, onLoaderComplete);
					onComplete(event.target.content);
				}
			}
		}
		
		private function addBitmap(name : String,bitmap : Bitmap) : void
		{
			bitmapdict[name] = bitmap;
		}
		
		public function get bitmapDict() : Dictionary
		{
			return bitmapdict;
		}
	}
}